#ifndef NETWORKSERVICES_HSOCKET_H
#define NETWORKSERVICES_HSOCKET_H

#include <string>
#include <cstring>
#include <cstdlib>
#include <utility>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syslog.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/timerfd.h>

#define QX_INLINE static inline

QX_INLINE int Blocking(int fd)
{
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
}

QX_INLINE int Nonblocking(int fd)
{
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}

QX_INLINE void SetTcpNoDelay(int fd, int on)
{
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char *) &on, sizeof(int)); // TCP_NODELAY包含头文件 <netinet/tcp.h>
}

QX_INLINE void SetReuseAddr(int fd, int on)
{
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(int));
}

QX_INLINE void SetReusePort(int fd, int on)
{
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const char *) &on, sizeof(int));
}

QX_INLINE void SetKeepAlive(int fd, int on)
{
    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &on, sizeof(int));
}

QX_INLINE int CloseSocket(int fd)
{
    return close(fd);
}

#ifndef SAFE_CLOSESOCKET
#define SAFE_CLOSESOCKET(fd)  do {if ((fd) >= 0) {CloseSocket(fd); (fd) = -1;}} while(0)
#endif

struct Socket {
    int fd = -1;
    std::string ip;
    uint16_t port;
};

static int CreateSocket()
{
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0) {
        syslog(LOG_ERR, "failed to create socket. errno:%d\n", errno);
        exit(-1);
    }
    return fd;
}

static int CreateNonBlockSocket()
{
    int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
    if (fd < 0) {
        syslog(LOG_ERR, "failed to create nonblocking socket. errno:%d\n", errno);
        exit(-1);
    }
    return fd;
}

static void Bind(int fd, const std::string &ip, uint16_t port)
{
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
    serverAddr.sin_port = htons(port);
    if (bind(fd, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
        syslog(LOG_ERR, "failed to bind. errno:%d\n", errno);
        close(fd);
        exit(-1);
    }
}

static void Listen(int fd, int num = 128)
{
    if (listen(fd, num) < 0) {
        syslog(LOG_ERR, "failed to listen. errno:%d\n", errno);
        close(fd);
        exit(-1);
    }
}

static int Accept(int fd, std::string &ip, uint16_t &port)
{
    struct sockaddr_in clientSock;
    socklen_t len = sizeof(clientSock);
    int clientFd = ::accept4(fd, (struct sockaddr *) &clientSock, &len, SOCK_NONBLOCK);
    ip = inet_ntoa(clientSock.sin_addr);
    port = ntohs(clientSock.sin_port);
    return clientFd;
}

static bool Connect(int fd, const std::string &ip, int hostPort)
{
    struct sockaddr_in sockaddrIn;
    memset(&sockaddrIn, 0, sizeof(sockaddrIn));
    sockaddrIn.sin_family = AF_INET;
    sockaddrIn.sin_addr.s_addr = inet_addr(ip.c_str());
    sockaddrIn.sin_port = htons(hostPort);
    if (connect(fd, (struct sockaddr *) &sockaddrIn, sizeof(sockaddrIn)) < 0) {
        syslog(LOG_ERR, "failed to connect sock. errno:%d\n", errno);
        return false;
    }
    return true;
}


#endif //NETWORKSERVICES_HSOCKET_H
